<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 
<html>
<head>
    <link rel="Stylesheet" type="text/css" href="../style.css" />
    <title>OpenSSL/SM2系列(1) —— OpenSSL如何支持SM2算法</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="pragma" content="no-cache" />

    <script type="text/javascript" src="../js/sh_main.min.js"></script>
    <link type="text/css" rel="stylesheet" href="../css/sh_vim.min.css" />

    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
</head>
<body onload="sh_highlightDocument('../js/lang/', '.min.js');">
    <div align="center" 
            style="z-index:100; width:100%; top:5px; position:fixed;">
        <code style="background-color:#e0e0e0">
            <strong>
            <a href="../index.html">回主页</a>
            <a href="../diary/diary.html">我的日志</a>
            <!-- <a href="mailto:jzgnh@foxmail.com">联系我</a> -->
            </strong>
        </code>
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
    </div>
    <div class="content">
    


<h2 id="toc_0.1"><code>OpenSSL&amp;SM2</code>系列(1) —— <code>OpenSSL</code>如何支持<code>SM2</code>算法</h2>

<hr />

<p>
本文基于<code>OpenSSL 1.1.1</code>版，<code>SM2</code>的操作代码参考<a href="https://www.openssl.org/docs/man1.1.1/man7/SM2.html">SM2(7)</a>，
本文按照此文档进行<code>SM2</code>算法的代码编写，因为此版本的<code>OpenSSL</code>在国密算法的支持上并没有非常完善，
后续还需要增加一些附加的代码才能正常使用。
</p>

<p>
本文中我们将生成一个服务国密标准的证书请求，基于此代码可以完成国密<code>SM2</code>算法的证书签发。
</p>


<h3 id="toc_0.1.1"><code>SM2</code>算法的基本操作</h3>

<p>
<code>SM2</code>的基本密码操作在此版本的<code>OpenSSL</code>已经可以正常支持，包括密钥的生成、签名等。
</p>

<ul>
<li>
密钥生成示例代码

</ul>

<pre class="sh_c">
#include &lt;openssl/evp.h&gt;
#include &lt;openssl/ec.h&gt;

EVP_PKEY *GMEx_GenSM2Key(void)
{
    EVP_PKEY* sm2k;
    EC_KEY *eck = EC_KEY_new_by_curve_name(NID_sm2);
    //注意这里判断 eck 的值

    EC_KEY_generate_key(eck); // return 1 on success

    sm2k = EVP_PKEY_new();
    //注意这里判断 sm2k 的值

    //EVP_PKEY_assign(sm2k, EVP_PKEY_SM2, eck);
    EVP_PKEY_set_alias_type(sm2k, EVP_PKEY_SM2);
    return sm2k;
}
</pre>

<ul>
<li>
<code>SM2</code>签名准备

</ul>

<p>
<code>SM2</code>的签名过程需要计算<code>Z</code>值，需要使用<code>ID</code>/曲线参数/公钥计算得到，
在国密的《SM2密码算法使用规范》中规定了<code>ID</code>使用<code>"1234567812345678"</code>。
<code>OpenSSL 1.1.1</code>中使用<code>EVP_PKEY_CTX</code>来支持<code>SM2</code>签名过程中的<code>Z</code>值计算。
</p>

<p>
所以在做签名之前我们首先需要执行的就是准备<code>EVP_MD_CTX</code>，下面是代码片段：
</p>
<pre class="sh_c">
EVP_MD_CTX* GMEx_PrepareSM2MDCtx(EVP_PKEY *pkey, EVP_MD_CTX* mctx)
{
    EVP_MD_CTX* rctx = NULL;

    if (mctx == NULL)
        rctx = mctx = EVP_MD_CTX_new();

    if (!mctx)
        return NULL;

    if (EVP_PKEY_id(pkey)!=EVP_PKEY_SM2)
        return mctx;

    EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new(pkey, NULL);
    if (pctx==NULL) {
        if (rctx) EVP_MD_CTX_free(rctx);
        return NULL;
    }

    EVP_MD_CTX_set_pkey_ctx(mctx, pctx);

    // ID 直接写成了固定的
    if (0&gt;=EVP_PKEY_CTX_set1_id(pctx, "1234567812345678", 16)) {
        if (rctx) EVP_MD_CTX_free(rctx);
        return NULL;
    }

    return mctx;
}
</pre>

<ul>
<li>
<code>SM2</code>签名操作

</ul>

<p>
签名验签可以使用<code>EVP_DigestSignInit</code>/<code>EVP_DigestSignUpdate</code>/<code>EVP_DigestSignFinal</code>函数完成，
下面示例代码用于生成一个SM2证书请求：
</p>
<pre class="sh_c">
static X509_REQ *gen_csr(EVP_PKEY *pkey)
{
    int r;
    X509_REQ *req;
    X509_NAME *subject;
    EVP_MD_CTX* mctx;

    req = X509_REQ_new();

    X509_REQ_set_version(req, 0);

    subject = X509_NAME_new();

    X509_NAME_add_entry_by_txt(subject,"CN",MBSTRING_UTF8,(unsigned char*)"12345678",-1,-1,0);
    X509_NAME_add_entry_by_txt(subject,"C",MBSTRING_ASC,(unsigned char*)"CN",-1,-1,0);

    X509_REQ_set_subject_name(req,subject);
    X509_NAME_free(subject);

    X509_REQ_set_pubkey(req,pkey);

    // 创建 EVP_MD_CTX
    mctx = GMEx_PrepareSM2MDCtx(pkey, NULL);

    // 初始化签名
    EVP_DigestSignInit(mctx, NULL, EVP_PKEY_id(pkey)==EVP_PKEY_SM2 ? EVP_sm3() : EVP_sha256(), NULL, pkey);

    r = X509_REQ_sign_ctx(req,mctx);
    if(r==0){
        X509_REQ_free(req);
        return NULL;
    }

    return req;
}
</pre>

<p>
此代码就是完整的代码，但是实际执行其实是会出错的（<code>EVP_DigestSignInit</code>），
这个就是因为<code>OpenSSL 1.1.1</code>没有设定<code>SM2-with-SM3</code>签名算法的<code>OID</code>而已。
</p>

<ul>
<li>
完善<code>SM2-with-SM3</code>的<code>OID</code>

</ul>

<p>
为了能够正确的完成此签名操作，我们需要在程序启动时完成<code>SM2-with-SM3</code>注册。
</p>

<pre class="sh_c">
int GMEx_InitLib(void)
{
    int NID_sm2dsa_with_sm3 = OBJ_create("1.2.156.10197.1.501", "SM2-with-SM3", "SM2-with-SM3");

    // EVP_PKEY_SM2 的 baseid 是 EVP_PKEY_EC，所以这里需要使用 EVP_PKEY_EC
    OBJ_add_sigid(NID_sm2dsa_with_sm3, NID_sm3, EVP_PKEY_EC);
}
</pre>

<p>
注册算法后上面代码就可以正常执行了。
</p>

<ul>
<li>
<code>SM2</code>的验签操作

</ul>

<p>
因为<code>X509_REQ_verify</code>并没有<code>ctx</code>版本，所以验签并不能如签名操作一样简单，
其代码如下：
</p>

<pre class="sh_c">
static int csr_verify(X509_REQ *req)
{
    unsigned char dat[2048];
    unsigned char *pd = dat;
    int r;

    EVP_MD_CTX* mctx;
    ASN1_BIT_STRING *psig;

    EVP_PKEY *pk = X509_REQ_get0_pubkey(req);

    if (EVP_PKEY_id(pk)!=EVP_PKEY_SM2)
        return X509_REQ_verify(req, pk);

    //FIXME: 这里完全是基于OpenSSL源代码确定的
    //        REQ 中的 REQ_INFO 偏移量为0才能运行下述代码
    r = i2d_X509_REQ_INFO((X509_REQ_INFO*)req, &amp;pd);
    if (r&lt;0) return -1;

    mctx = GMEx_PrepareSM2MDCtx(pk, NULL);
    if (!mctx) return -1;

    // 此处代码并未处理函数错误
    X509_REQ_get0_signature(req, (const ASN1_BIT_STRING**)&amp;psig, NULL);

    EVP_DigestVerifyInit(mctx, NULL, EVP_sm3(), NULL, pk);
    EVP_DigestVerifyUpdate(mctx, dat, r);

    r = EVP_DigestVerifyFinal(mctx, (const unsigned char*)psig-&gt;data,
                                (size_t)psig-&gt;length);
    EVP_MD_CTX_free(mctx);

    return r;
}
</pre>


<h3 id="toc_0.1.2">上述代码遗留的问题</h3>

    </div>
    
<hr/>
<div align="center" style="width:100%; top:5px">
<span>©2020-2021 扯淡老焦</span>
</div>

</body>
</html>
